package com.stary.pay.alipay;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeCancelRequest;
import com.alipay.api.request.AlipayTradeCloseRequest;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradeOrderinfoSyncRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeCancelResponse;
import com.alipay.api.response.AlipayTradeCloseResponse;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeOrderinfoSyncResponse;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.stary.pay.alipay.config.IAlipayConfig;
import com.stary.pay.alipay.dto.request.AlipayOrderSync;
import com.stary.pay.alipay.dto.request.AlipayRefund;
import com.stary.pay.alipay.dto.request.AlipayUnifiedorder;
import com.stary.pay.alipay.dto.response.AlipayTradeNotifyResponse;
import com.stary.pay.alipay.util.AlipayUtils;

/**
 * <p>支付宝支付相关业务</p>
 * @author stary {@link stary1993@qq.com}
 * @since 2019-5-7
 */
public class Alipay {

	private static final Logger logger = LoggerFactory.getLogger(Alipay.class);
	
	/**
	 * 支付宝配置接口
	 */
	private IAlipayConfig alipayConfig;
	/**
	 * 支付宝请求客户端
	 */
	private AlipayClient alipayClient;


	public Alipay() {
		super();
	}

	public Alipay(IAlipayConfig alipayConfig, AlipayClient alipayClient) {
		super();
		this.alipayConfig = alipayConfig;
		this.alipayClient = alipayClient;
	}

	public IAlipayConfig getAlipayConfig() {
		return alipayConfig;
	}

	public void setAlipayConfig(IAlipayConfig alipayConfig) {
		this.alipayConfig = alipayConfig;
	}

	public AlipayClient getAlipayClient() {
		return alipayClient;
	}

	public void setAlipayClient(AlipayClient alipayClient) {
		this.alipayClient = alipayClient;
	}

	/**
	 * <p>统一收单交易关闭</p>
	 * 用于交易创建后，用户在一定时间内未进行支付，可调用该接口直接将未付款的交易进行关闭。
	 * @param trade_no 该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。和out_trade_no不能同时为空，如果同时传了 out_trade_no和 trade_no，则以 trade_no为准。
	 * @param out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no 。
	 * @param operator_id 卖家端自定义的的操作员 ID（可选）
	 * @return {@link AlipayTradeCloseResponse}
	 */
	public AlipayTradeCloseResponse tradeClose(String trade_no, String out_trade_no, String operator_id) {
		AlipayTradeCloseResponse response = null;
		JSONObject bizContent = new JSONObject();
		bizContent.put("trade_no", trade_no);
		bizContent.put("out_trade_no", out_trade_no);
		bizContent.put("operator_id", operator_id);
		AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);			
		} catch (AlipayApiException e) {
			logger.error("tradeClose() error:{}", e);
		}
		return response;		
	}

	/**
	 * <p>统一收单交易撤销</p>
	 * 支付交易返回失败或支付系统超时，调用该接口撤销交易。如果此订单用户支付失败，支付宝系统会将此订单关闭；如果用户支付成功，支付宝系统会将此订单资金退还给用户。 <br/>
	 * 注意：只有发生支付系统超时或者支付结果未知时可调用撤销，其他正常支付的单如需实现相同功能请调用申请退款API。 <br/>
	 * 提交支付交易后调用【查询订单API】，没有明确的支付结果再调用【撤销订单API】。
	 * @param trade_no 该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。和out_trade_no不能同时为空，如果同时传了 out_trade_no和 trade_no，则以 trade_no为准。
	 * @param out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no 。
	 * @return {@link AlipayTradeCancelResponse}
	 */
	public AlipayTradeCancelResponse tradeCancel(String trade_no,String out_trade_no) {
		AlipayTradeCancelResponse response = null;
		JSONObject bizContent = new JSONObject();
		bizContent.put("trade_no", trade_no);
		bizContent.put("out_trade_no", out_trade_no);
		AlipayTradeCancelRequest request = new AlipayTradeCancelRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);			
		} catch (AlipayApiException e) {
			logger.error("tradeCancel() error:{}", e);
		}
		return response;		
	}
	/**
	 * <p>支付宝订单信息同步</p>
	 * 该接口用于商户向支付宝同步该笔订单相关业务信息。
	 * @param orderinfoSync 订单同步请求参数 {@link AlipayOrderSync}
	 * @return {@link AlipayTradeOrderinfoSyncResponse}
	 */
	public AlipayTradeOrderinfoSyncResponse tradeOrderinfoSync(AlipayOrderSync orderinfoSync) {
		AlipayTradeOrderinfoSyncResponse response = null;
		JSONObject bizContent = (JSONObject) JSONObject.toJSON(orderinfoSync);
		AlipayTradeOrderinfoSyncRequest request = new AlipayTradeOrderinfoSyncRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);			
		} catch (AlipayApiException e) {
			logger.error("tradeOrderinfoSync() error:{}", e);
		}
		return response;		
	}
	/**
	 * <p>统一收单线下交易预创建(扫码支付)</p>
	 * 收银员通过收银台或商户后台调用支付宝接口，生成二维码后，展示给用户，由用户扫描二维码完成订单支付。
	 * @param unifiedorder 统一下单请求参数  {@link AlipayUnifiedorder}
	 * @return {@link AlipayTradePrecreateResponse}
	 */
	public AlipayTradePrecreateResponse tradePrecreate(AlipayUnifiedorder unifiedorder) {
		AlipayTradePrecreateResponse response = null;
		JSONObject bizContent = (JSONObject) JSONObject.toJSON(unifiedorder);
		AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);			
		} catch (AlipayApiException e) {
			logger.error("tradePrecreate() error:{}", e);
		}
		return response;		
	}
	/**
	 * <p>统一收单交易创建</p>
	 * 商户通过该接口进行交易的创建下单。
	 * @param unifiedorder 统一下单请求参数  {@link AlipayUnifiedorder}
	 * @return {@link AlipayTradeCreateResponse}
	 */
	public AlipayTradeCreateResponse tradeCreate(AlipayUnifiedorder unifiedorder) {
		AlipayTradeCreateResponse response = null;
		JSONObject bizContent = (JSONObject) JSONObject.toJSON(unifiedorder);
		AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);			
		} catch (AlipayApiException e) {
			logger.error("tradeCreate() error:{}", e);
		}
		return response;		
	}
	/**
	 * <p>统一收单线下交易查询</p>
	 * 该接口提供所有支付宝支付订单的查询，商户可以通过该接口主动查询订单状态，完成下一步的业务逻辑。 <br/>
	 * 需要调用查询接口的情况： <br/>
	 * 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知； <br/>
	 * 调用支付接口后，返回系统错误或未知交易状态情况； <br/>
	 * 调用alipay.trade.pay，返回INPROCESS的状态； <br/>
	 * 调用alipay.trade.cancel之前，需确认支付状态；
	 * @param trade_no 该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。和out_trade_no不能同时为空，如果同时传了 out_trade_no和 trade_no，则以 trade_no为准。
	 * @param out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no 。
	 * @return {@link AlipayTradeQueryResponse}
	 */
	public AlipayTradeQueryResponse tradeQuery(String out_trade_no,String trade_no) {
		AlipayTradeQueryResponse response = null;
		JSONObject bizContent = new JSONObject();
		bizContent.put("out_trade_no", out_trade_no);
		bizContent.put("trade_no", trade_no);
		AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);
		} catch (AlipayApiException e) {
			logger.error("tradeQuery() error:{}", e);
		}
		return response;		
	}

	/**
	 * <p>统一收单交易退款</p>
	 * 当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家，支付宝将在收到退款请求并且验证成功之后，按照退款规则将支付款按原路退到买家帐号上。 
	 * 交易超过约定时间（签约时设置的可退款时间）的订单无法进行退款 支付宝退款支持单笔交易分多次退款，多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。
	 * 一笔退款失败后重新提交，要采用原来的退款单号。总退款金额不能超过用户实际支付金额。
	 * @param refund 退款请求参数 {@link AlipayRefund}
	 * @return {@link AlipayTradeRefundResponse}
	 */
	public AlipayTradeRefundResponse tradeRefund(AlipayRefund refund) {
		AlipayTradeRefundResponse response = null;
		JSONObject bizContent = (JSONObject) JSONObject.toJSON(refund);
		AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);
		} catch (AlipayApiException e) {
			logger.error("tradeRefund() error:{}", e);
		}
		return response;
	}
	/**
	 * <p>统一收单交易退款查询</p>
	 * 商户可使用该接口查询自已通过alipay.trade.refund提交的退款请求是否执行成功。 该接口的返回码10000，仅代表本次查询操作成功，不代表退款成功。
	 * 如果该接口返回了查询数据，则代表退款成功，如果没有查询到则代表未退款成功，可以调用退款接口进行重试。重试时请务必保证退款请求号一致。
	 * @param trade_no 该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。和out_trade_no不能同时为空，如果同时传了 out_trade_no和 trade_no，则以 trade_no为准。
	 * @param out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no 。
	 * @param out_request_no 请求退款接口时，传入的退款请求号，如果在退款请求时未传入，则该值为创建交易时的外部交易号。 
	 * @return {@link AlipayTradeFastpayRefundQueryResponse}
	 */
	public AlipayTradeFastpayRefundQueryResponse tradeFastpayRefundQuery(String trade_no,String out_trade_no,String out_request_no) {
		AlipayTradeFastpayRefundQueryResponse response = null;
		JSONObject bizContent = new JSONObject();
		bizContent.put("trade_no", trade_no);
		bizContent.put("out_trade_no", out_trade_no);
		bizContent.put("out_request_no", out_request_no);
		AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.execute(request);
		} catch (AlipayApiException e) {
			logger.error("tradeFastpayRefundQuery() error:{}", e);
		}
		return response;
	}

	/**
	 * <p>统一收单下单并支付页面</p>
	 * PC场景下单并支付
	 * @param unifiedorder 统一下单请求参数  {@link AlipayUnifiedorder}
	 * @return {@link AlipayTradePagePayResponse}
	 */
	public AlipayTradePagePayResponse tradePagePay(AlipayUnifiedorder unifiedorder) {
		AlipayTradePagePayResponse response = null;
		JSONObject bizContent = (JSONObject) JSONObject.toJSON(unifiedorder);
		AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
		request.setReturnUrl(alipayConfig.returnUrl());
		request.setNotifyUrl(alipayConfig.notifyUrl());
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.pageExecute(request);
		} catch (AlipayApiException e) {
			logger.error("tradePagePay() error:{}", e);
		}
		return response;
	}
	/**
	 * <p>统一收单下单并支付页面</p>
	 * WAP场景下单并支付
	 * @param unifiedorder 统一下单请求参数  {@link AlipayUnifiedorder}
	 * @return {@link AlipayTradeWapPayResponse}
	 */
	public AlipayTradeWapPayResponse tradeWapPay(AlipayUnifiedorder unifiedorder) {
		AlipayTradeWapPayResponse response = null;
		JSONObject bizContent = (JSONObject) JSONObject.toJSON(unifiedorder);
		AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
		request.setReturnUrl(alipayConfig.returnUrl());
		request.setNotifyUrl(alipayConfig.notifyUrl());//在公共参数中设置回跳和通知地址
		request.setBizContent(bizContent.toString());
		try {
			response = alipayClient.pageExecute(request);
		} catch (AlipayApiException e) {
			logger.error("tradeWapPay() error:{}", e);
		}
		return response;
	}
	/**
	 * <p>支付异步通知数据</p>
	 * @param request {@link HttpServletRequest}
	 * @return {@link AlipayTradeNotifyResponse}
	 */
	public AlipayTradeNotifyResponse tradeNotify(HttpServletRequest request) {
		AlipayTradeNotifyResponse response = null;
		boolean signVerified = false;
		Map<String, String[]> aliParams = request.getParameterMap(); 
		//用以存放转化后的参数集合
		Map<String, String> conversionParams = new HashMap<>(); 
		for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext();) { 
			String key = iter.next(); 
			String[] values = aliParams.get(key); 
			String valueStr = ""; 
			for (int i = 0; i < values.length; i++) { 
				valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; 
			} 
			conversionParams.put(key, valueStr); 
		}
		try {
			//调用SDK验证签名
			signVerified = AlipaySignature.rsaCheckV1(conversionParams, alipayConfig.alipayPublicKey(), AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
		} catch (AlipayApiException e) {
			logger.error("tradeNotify() signVerified error:{}", e);
		}
		if (signVerified) {
			try {
				response = (AlipayTradeNotifyResponse) AlipayUtils.mapToObject(conversionParams, AlipayTradeNotifyResponse.class);
			} catch (Exception e) {
				logger.error("tradeNotify() notifyResult error:{}", e);
			}
		}
		return response;
	}
}
